Istio VirtualService

개요

이스티오의 대표적인 리소스로, 트래픽 라우팅 설정을 하는데 사용된다.
이름이 가상 서비스이지만, 실질적으론 쿠버네티스의 인그레스를 대체하는 리소스 정도로 이해하는 게 좋다.

기능

기본이 되는 리소스인 만큼, 기능이 정말 많다.
상세한 설정법은 아래에서 다루고, 일단 어떤 기능들이 있는지만 간략하게 보자.

다양한 기능을 각 용도에 따라 분류해봤다.

동작

이 리소스를 만들었을 때 이뤄지는 이스티오의 동작 흐름은 다음과 같다.

버츄얼 서비스는 트래픽 라우팅 관련 설정을 정의하기에, 엔보이에 대해서 RDS 쪽을 건드리게 된다.
이때 데이터 플레인의 모든 엔보이를 설정해버리는 건 아니고 먼저 대상이 될 엔보이를 한정짓게 된다.

목적지 업스트림에는 클러스터를 지정해야 하는데, 기본적으로는 서비스로 지정하는 편이다.
서비스 메시 환경에서 해당 서비스에 대한 클러스터가 레지스트리로 전부 등록이 돼있어 서비스를 지정하더라도 그냥 클러스터를 지정하는 것과 다를 게 없다.
여기에 이스티오 데스티네이션룰을 설정하여 대상이 되는 클러스터를 부분집합(subset)으로 조금 더 세분화하는 게 가능하다.
이스티오 서비스엔트리로 레지스트리에 등록된 클러스터를 지정할 수도 있다.

양식 작성법

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  gateways:
  - mesh
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v3

간단하게는 이런 식으로 세팅할 수 있다.
각 필드 별로 자세히 보자.

hosts

버츄얼서비스가 적용될 대상 호스트를 지정하는 필드로, 엔보이에서 RDS의 가상 호스트 부분을 설정하게 된다.
웬만해서 FQDN으로 설정하는 게 좋은데, 네임스페이스 별 규칙에 따라 FQDN이 완성되어 이를 기반으로 대상이 될 가상 호스트를 결정하기 때문이다.
그냥 reviews 이런 식으로 세팅하면 다른 네임스페이스에 있는 reviews 서비스로 접근을 못 한다던가 하는 오동작을 하게 된다.

gateways

  gateways:
  - mesh
  - ingress-gateway

버츄얼서비스의 설정이 적용될 엔보이를 결정짓는 필드이다.
단순하게 보자면 이 버츄얼서비스의 대상이 될 업스트림으로 트래픽을 보낼 다운스트림이 무엇인지 지정하는 필드이다.
이렇게 해석하면 버츄얼서비스로 들어오는 진입점(게이트웨이)을 지정한다는 의미로서 조금 더 직관적으로 이해할 수 있다.
그렇지만 실제 동작 방식을 따지자면 어디까지나 이 필드는 설정을 적용하고자 하는 엔보이가 무엇인지를 나타내는 것임을 유의하자.

가상 서비스의 의미

이 필드를 정확하게 이해하면 이름이 왜 가상인지도 이해할 수 있다.
이스티오는 서비스 메시로서 각 프록시가 트래픽을 보내야 할 대상을 알고 라우팅하는 프록시#리버스 프록시 방식을 택하고 있다.
흔히 생각하는 서비스는 트래픽을 받을 측에서 설정하는 것으로 흔히 생각되고, 이 리소스 역시 마치 트래픽을 받는 쪽에 설정을 할 것처럼 생겼다.
그러나 실상 이 리소스는 트래픽을 보내는 대상을 추적한 다음, 그 대상의 설정을 바꾸는 식으로 동작하기에 서비스 같지만 실상은 가상인 것이다.

여기에 이스티오 게이트웨이를 넣어주면 해당 게이트웨이에 해당하는 엔보이의 RDS에 설정이 적용된다.
mesh라는 값을 넣을 수도 있는데, 이건 현재 서비스 메시 내의 모든 엔보이 사이드카를 대상으로 하겠다는 뜻이다.
게이트웨이를 넣을 때 {게이트웨이 네임스페이스}/{게이트웨이 이름}과 같은 식으로 어떤 네임스페이스의 게이트웨이인지 명시할 수 있다.

exportTo

  exportTo:
  - '.'

gateways 필드에서는 서비스 메시 내의 모든 프록시에 적용할 때 mesh를 사용한다고 했는데, 즉 어떤 네임스페이스에 있더라도 모든 프록시에 적용된다는 말이다.
이걸 조금 더 세분화해서 특정 네임스페이스를 한정짓고자 할 때는 exportTo를 사용한다.
명시하지 않으면 '*'가 사용되는데, 그냥 모든 네임스페이스에 적용되게 하겠다는 뜻이다.
위처럼 '.'를 설정하면 버츄얼서비스를 만든 네임스페이스의 프록시만 대상이 된다.

http - 기본

버츄얼서비스의 핵심 설정들을 넣게 되는 필드로, 프로토콜 별로 작성할 수 있다.

이중에서 설정을 많이 할 수 있는 http 필드를 집중적으로 정리하겠다.

  http:
    # 이름은 디버깅이나 관리용이고, 실제 영향은 없다.
  - name: product
    # 조건 매칭
    match:
    - uri:
        prefix: "/productpage"
	# uri 재작성
	rewrite:
      uri: "/newcatalog"
    # 라우팅 설정
    route:
    - destination:
        host: reviews # reviews.default.svc.cluster.local로 해석된다.
        subset: v2
  - match:
    - uri:
        prefix: "/reviews"
    # 다른 버츄얼서비스로 트래픽 위임
    delegate:
        name: reviews
        namespace: nsB
  - route:
    - destination:
        host: events
        subset: v3

여기에 정말 다양한 설정을 넣을 수 있는데, 일단 전체 구조는 리스트로 작성한다.
이 리스트의 원소들은 순서대로 적용되기 때문에 세부적인 조건이나 설정이 담긴 라우팅 설정일수록 위에 배치하는 것이 좋다.

route

  - route:
    - destination:
        host: reviews.prod.svc.cluster.local
        subset: v2
      weight: 25
    - destination:
        host: reviews.prod.svc.cluster.local
        subset: v1
      weight: 75

라우팅할 업스트림 클러스터를 지정하는 핵심 필드는 route로, 여기에도 대상 목적지를 리스트로 작성한다.
destination.host에 대상 클러스터(서비스, 서비스 엔트리, 데룰)를 명시해주면 된다.
destination.subset이란 필드가 보이는데, 이것은 Istio DestinationRule을 지정해야만 사용할 수 있는 방식으로 해당 클러스터를 부분집합으로 나누었을 때 어떤 부분집합으로 보낼지에 대한 설정을 한다.
weight 필드로 가중치를 설정할 수 있는데, 이걸 보면 알겠지만 여기에는 리스트 내의 순서는 적용되지 않으니 참고.

matches

  - match:
    - name: jason
      headers:
        end-user:
          exact: jason
      uri:
        prefix: "/ratings/v2/"
      ignoreUriCase: true

어떤 트래픽을 매칭할지 지정하는 필드이다.
각 필드를 설명하기 전에, 조건 매칭을 하는 필드들에서 가능한 선택지는 3개가 있다.

이런 조건 매칭을 하는 필드들에 대해서는 굵게 표시한다.

이를 기반으로 다음의 필드들을 보자.

이렇게나 많은 매칭 관련 설정을 할 수 있다..

    - match: 
        - port: 8443
          destinationSubnets:
          - 172.31.0.32
          - 127.0.0.0/16
          sniHosts:
            - simple-sni-1.istioinaction.io

참고로 tcp, tls 필드는 여기에서 다운스트림 관련 매칭 정도만 넣을 수 있다.
destinationSubnets로 대상 서브넷을 지정할 수 있고, tls는 sniHosts를 통해 호스트 기반 매칭이 가능하다.

headers

  http:
  - headers:
      request:
        set:
          test: "true"
	route:          
    - destination:
        host: reviews.prod.svc.cluster.local
        subset: v1
      headers:
        response:
          remove:
          - foo

헤더 관련 조작을 하는 필드로, http, http.routes 필드 아래 들어갈 수 있다.
request, response 각각에 설정을 가할 수 있고, set, add, remove 동작을 할 수 있다.
set은 기존 값을 덮어쓰기하고 add는 기존 값에 추가하는 방식이다!

http - 라우팅 관련

위에 까지가 기본적으로 http 필드에서 작성할 수 있는 설정들을 알아봤다.
route 필드가 가장 기본적인 라우팅 관련 필드이나, 사실 더 많은 설정이 가능하다.

delegate

    delegate:
        name: reviews
        namespace: nsB

다른 버츄얼서비스로 트래픽을 보내는 필드이다.
위임은 한 홉밖에 안 되도록 막혀있으니 괜히 이걸로 복잡하게 짤 생각하지 말자.

redirect

    redirect:
      uri: /v1/bookRatings
      authority: newratings.default.svc.cluster.local
	  derivePort: FROM_REQUEST_PORT
	  redirectCode: 308

리다이렉트를 시키는 필드이다.
uri는 말 그대로 어디로 가야하는지를 나타낸다. authority 필드는 해당 요청의 Authority, Host 헤더를 조작해준다.
derivePort는 들어온 요청이 어느 포트로 리다이렉트돼야 하는지 지정하는데 위 설정은 그냥 처음 들어온 포트 그대로 가라는 뜻이다.
FROM_PROTOCOL_DEFAULT를 쓰게 되면 http, https의 기본 포트로 리다이렉트시킨다.
그냥 port 필드를 넣어서 명시적으로 포트를 지정해줘도 된다.
기본 리다이렉트 상태 코드는 301이나, 위처럼 원하는대로 바꿀 수도 있다.

directResponse

    headers:
      response:
        set:
          content-type: "text/plain"
    directResponse:
      status: 503
      body:
        string: "unknown error"
---		
        bytes: "dW5rbm93biBlcnJvcg==" # base64로 인코딩된 unknown error

즉각 응답을 때리는 필드이다.
바디로 넣을 값을 지정할 수 있는데, 보통 headers 필드로 text 형식이라 헤더를 설정해주는 게 좋다.

http - 기타

이밖에도 네트워크 복원력을 위한 각종 필드가 존재한다.

timeout

    timeout: 5s

말 그대로 요청이 최대 이어질 수 있는 시간을 말한다.
이 기간을 넘기면 다운스트림에는 500에러가 떨어진다.

retries

    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream

재시도 설정을 하는 필드이다.
여기에도 타임아웃을 세팅하는 필드가 있는데, 위의 전역적인 타임아웃 세팅은 개별 재시도의 시간까지 합산하여 계산하도록 돼있다.
retryOn에는 쉼표를 기준으로 재시도할 상황들을 넣어주면 된다.
5xx라고 쓰면 모든 500번 에러에 대해 재시도해준다.
retryRemoteLocalities 필드를 bool로 설정할 수 있는데, 이건 원격지에 대해서도 재시도를 할지 설정한다.
원격지 개념은 나중에 다루겠다.

attempts: 2
retryOn: "connect-failure,refused-stream,unavailable,cancelled,503"

참고로 기본 이스티오 mesh.config.defaultHttpRetryPolicy 설정에는 이렇게 세팅돼있다.

rewrite

  - match:
    - uri:
        prefix: /ratings
    rewrite:
      uri: /v1/bookRatings

uri를 rewrite하는 필드.
매칭할 때 접두사를 기반으로 했다면 접두사 부분만 바꿔치기해준다.

    rewrite:
      uriRegexRewrite:
	    match:  "^/service/([^/]+)(/.*)$"
	    rewrite: "/customprefix/\2/\1"

조금 더 상세하게 rewrite를 하려면 이렇게 정규식을 쓴다.
위 예시는 /service/update/v1/api이란 uri를 /customprefix/v1/api/update로 바꿔버릴 것이다.

fault

    fault:
      delay:
        percentage:
          value: 0.1
        fixedDelay: 5s
	  abort:
        percentage:
          value: 0.1
        httpStatus: 400

확률적으로 에러를 주입하는 필드이다.
실제 트래픽의 일부에 대해 에러를 발생시키는 것이니까 매칭을 명확히 해서 테스트용 트래픽에 대해서만 하는 게 좋다.
delay와 abort가 있는데, 위처럼 설정해도 각각의 확률은 독립적으로 처리된다.
참고로 여기에서 value 0.1은 0.001퍼센트를 의미한다!
10퍼센트라고 헷갈리지 말자.

mirror

    mirror:
      host: reviews-v2
    mirrorPercentage:
      value: 20.0  # 20% 요청만 미러링

트래픽을 복제해서 다른 곳으로도 보내는 필드이다.
이때 미러링된 측의 응답은 무시되기에 실제 트래픽에는 영향을 끼치지 않는다.

    mirrors:
    - desination: review
      percentage: 20.0

이렇게 리스트 형태로 작성할 수도 있다.

corsPolicy

    corsPolicy:
      allowOrigins:
      - exact: https://example.com
      allowMethods:
      - POST
      - GET
      allowCredentials: false
      allowHeaders:
      - X-Foo-Bar
      maxAge: "24h"

cors 설정하는 필드.
아무래도 프록시로 기능하다보니 클라 단에서 cors가 나는 경우가 있을 텐데 이걸 설정해주면 된다.
어플리케이션에 미들웨어 세팅을 안 해도 된다는 게 엄청 좋은 것 같다..

관련 문서

이름 noteType created
Istio Gateway knowledge 2025-04-16
Istio ServiceEntry knowledge 2025-04-17
Istio VirtualService knowledge 2025-04-21
Istio DestinationRule knowledge 2025-04-21
Istio Sidecar knowledge 2025-05-13
Istio ProxyConfig knowledge 2025-05-17
2W - 인그레스 게이트웨이 실습 published 2025-04-17
3W - 버츄얼 서비스를 활용한 기본 트래픽 관리 published 2025-04-22
3W - 트래픽 가중치 - flagger와 argo rollout을 이용한 점진적 배포 published 2025-04-22
3W - 트래픽 미러링 패킷 캡쳐 published 2025-04-22
3W - 서비스 엔트리와 이그레스 게이트웨이 published 2025-04-22
3W - 데스티네이션 룰을 활용한 네트워크 복원력 published 2025-04-26
3W - 타임아웃, 재시도를 활용한 네트워크 복원력 published 2025-04-26
6W - 이스티오 컨트롤 플레인 성능 최적화 published 2025-05-18
7W - 이스티오 메시 스케일링 published 2025-06-09
E-이스티오 컨트롤 플레인 성능 최적화 topic/explain 2025-05-18
E-이스티오 DNS 프록시 동작 topic/explain 2025-06-01
E-이스티오 메시 스케일링 topic/explain 2025-06-08

참고